<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<script>
    /*
    * 构造函数：和普通函数类似，只不过构造函数在执行过程中可能用到this，执行方法new一个函数，作用是生成对象
    *
    * 实例对象：有构造函数生成的对象
    *
    * 原型：分两种
    *   1.普通函数的原型：任何函数都有的prototype属性：函数在实例对象的时候，将原型添加到对象中
    *
    *   2.对象的隐式原型：任何的对象都有__proto__属性，对象的特点类别的信息载体
    *
    *   对象访问属性的流程：首先是在对象本身上去找，对象本身没有或者找不到，就去隐式原型__proto__中找，
    *                   如果隐式原型中找不到，就去隐式原型的隐式原型中找...
    *
    * */
    function Foo(){

    }

    //1.Foo.prototype 存在吗？ 存在，因为任何函数都有prototype属性
    console.log(Foo.prototype) //返回一个对象 Object

    //2.Foo.__proto__存在吗？ 存在，任何函数也是对象，对象身上都有__proto__属性
    console.log(Foo.__proto__) //返回一个函数 ƒ () { [native code] }

    //3.Foo.prototype 和 Foo.__proto__有啥区别
    /*
    * Foo.prototype 是 函数Foo 作为构造函数时 传递给实例对象的原型(类似于基因) (构造函数Foo是父，实例对象是子)
    *
    * Foo.__proto__ 是 函数Foo 作为对象时 从它自己的父辈中所继承的属性 (函数Foo是对象，是子辈)
    *
    * 函数Foo的 构造函数(函数Foo的父辈)是什么？
    *
    * */

    /*
    * 函数的构造函数是什么？谁创建了这个函数？
    *
    * 任何函数都是由同一个构造函数Function 所构造生成出来的
    * */

    function Foo2(){

    }

    console.log(Foo.call,Foo2.call) //函数Foo和Foo2，这两个不同的函数都有call方法，说明他们都是由一个共同祖先(构造者)所构造的

    console.log(Foo.constructor) //找到函数Foo的构造者 是一个函数 Function 这是所有函数的构造者

    //dir通过目录结构展开一个对象
    console.dir(Function.prototype) //构造函数的原型，里面有函数的所有方法

    //如果要给所有函数添加一个myMethod方法
    Function.prototype.myMethod = function(){
        console.log("run method")
    }

    Array.prototype.foo = function(){
        console.log(this) //this指向谁？会指向实例对象，谁调用指向谁
    }
    Array.prototype.foo() //是Array.prototype调用的函数foo，this指向Array.prototype

    let arr = []
    arr.foo() //this指向arr



    //按照 reduce功能 翻版设计 自己封装的 myReduce
    //原型开发，相当于自己设计想要的和原本调用方式一样的功能 arr.reduce(callback,inital)//回调函数，初始值
    let arr1 = [1,2,3,4]
    
    Array.prototype.myReduce = function(cb,total){ //没有修改原型的引用，只是在原型中添加一条属性myReduce
        //用result表示数组的迭代结果，默认初始值为数组下标为0的第0项，值为1(数组arr1 = [1,2,3,4])
        let result = this[0] //原型开发中，this指向实例对象即调用该方法的主体。
        //为什么用this？因为参数中没有传入数组，我们没法获得数组的具体内容，就要用this来指向具体调用者实例对象数组

        //用length表示数组的循环迭代次数
        let length = this.length

        //用start表示循环迭代的开始下标，后面for循环中是i的初始值
        let start = 1 //如果myReduce方法中值接收了一个参数即回调函数，那么迭代体total初始值就是数组中下标为0的第0项，
        //迭代开始的项item，就是数组中下标为1(下标1是start = 1这里来的)的第1项所对应的值2 (数组arr1 = [1,2,3,4])

        if(arguments.length >= 2){ //如果myReduce方法中接收两个以上参数的情况：回调函数cb，迭代体total的初始值等
            result = total //当设置了迭代体total的初始值时，迭代结果result的值就等于迭代体total的初始值
            start = 0 //迭代体total已经有了初始值，那么迭代开始的项item，就是数组中下标为0(下标0是start = 0这里来的)
            //的第0项所对应的值1 (数组arr1 = [1,2,3,4])
        }

        for(let i = start;i < length;i++){ //i表示循环中，开始迭代项的下标

            //最终的循环迭代结果等于回调函数cb的执行结果
            result = cb(result,this[i],i,this)
            //回调函数cb可接收四个参数：
            // 第一个参数：迭代体result(本来应该用total来表示，但是total值已经赋值给了result，
            // 并且这一次的迭代结果result的值将在下一次迭代中成为迭代体total的值，所以直接用result来表示了)
            // 第二个参数：数组中迭代的元素this[i]；第三个参数：迭代的元素的下标i；第四个参数：数组本身this

        }

        return result //循环结束后，将最终的迭代结果result返回出去
    }

    /*Array.prototype = { //修改了数组的原型，原型重新引用一个新的对象，但是原型之前的引用还存在，只是现在不指向原来的那个引用了
                        //所以这种方法是错误的
        myReduce(){

        }
    }*/

    console.log(arr1.myReduce((total, item) => total + item))
    console.log(arr1.reduce((total, item) => total + item))

    /*
    * 请使用原型的方式，封装以下方法：
    * forEach filter map every some ... push pop shift unshift
    *
    * */


    //Object Function window 三者之间的关系
    /*
    * window 是最小的，window是不是对象？任何对象都是由Object创建出来的，所以是三者中最小的
    *
    * Function 和 Object之间的关系
    *
    * 任何对象都是由Object直接或间接创建出来的，Object是负责创建对象的构造函数
    *
    * 任何函数都是由Function创建出来的，Function是负责创建函数的构造函数
    * Function也是函数，所以Function也是由Function创建出来的（其实Function的创建者是Object，
    * 但是为了维护任何函数都是由Function创建出来的，Object不得已把创建者的身份交给了Function，
    * 就导致了Function自己创建了自己）
    *
    * */
    console.log(Function.constructor === Function) //true 说明Function的构造者是Function 我创建了我自己

    console.log(Function.prototype === Function.__proto__)
    //true 构造函数Function的原型.prototype 传递给实例对象Function的隐式原型.__proto__
    //说明 Function即是自己的构造函数，又是自己的实例对象

    console.log(Function.prototype === Object.__proto__)
    //true 虽然Object的层级要比Function的层级高级，但是，两者分工不同，决定了内部归属权属于谁的问题
    //Object是负责创建对象的构造函数，Function是负责创建函数的构造函数，而Object既然是构造函数，就理应属于函数，
    //就不得不遵守任何函数都是由Function创建出来的这条规则，
    //所以才有了构造函数Function的原型.prototype 传递给实例对象Object的隐式原型.__proto__


    /*
    * 但是，Object.prototype才是基因的源泉
    *
    * Object.prototype.__proto__ 是原型链的终点(对象访问属性的终点)
    * 万物皆对象，万物访问属性的时候都会访问到Object.prototype中
    *
    * */
    console.log(Object.prototype) //数据类型是对象，所以Object.prototype是一个对象

    console.log(Object.prototype.constructor === Object)
    //true 既然Object.prototype是一个对象，那么Object.prototype的创建者就是Object

    console.log(Object.prototype.__proto__)
    //Object.prototype这个对象的基因（原型）的源头.__proto__是什么，结果是null，是虚无，表示源头不存在，其实源头就是它自己

    /*
    * 原型链：对象访问属性，如果本身访问不到，就去.__proto__上访问，如果还是没有，就去.__proto__的.__proto__中寻找，就这样一层一层
    *       地往原型上去寻找，最终的源头就是Object.prototype
    *
    * 例如：任何对象都有toString方法
    * */
    let obj1 = {}
    let obj2 = {}
    console.log(obj1.toString === obj2.toString) //true
    console.log(obj1.toString === Object.prototype.toString)
    //返回true 说明任何对象的.toString方法都是来自原型链的终点Object.prototype的.toString方法属性

    /*
    * 那么Object.prototype.toString方法 是不是适用于所有的数据类型？
    *   可以将任意的数据类型传进去，内部是使用了 this 来选择调用主体的
    * 例如：3..toString() 可以返回 "3"
    *
    * */

    /*
    * 鉴别所有数据类型的万能方法
    * 用同一个toString调用，返回的数据结构描述一致，用显示绑定call来绑定调用主体
    *
    * */
    console.log(Object.prototype.toString.call(3)) //[object Number]
    console.log(Object.prototype.toString.call("3")) //[object String]
    console.log(Object.prototype.toString.call(true)) //[object Boolean]
    console.log(Object.prototype.toString.call(undefined)) //[object Undefined]
    console.log(Object.prototype.toString.call(null)) //[object Null]
    console.log(Object.prototype.toString.call(function (){})) //[object Function]
    console.log(Object.prototype.toString.call(/sad/)) //[object RegExp]
    console.log(Object.prototype.toString.call({})) //[object Object]
    console.log(Object.prototype.toString.call(NaN)) //[object Number]
    console.log(Object.prototype.toString.call([])) //[object Array]

    /*
    * 我们可以自己封装这个鉴别方法
    * */
    function myType(value){ //传入什么数据类型，返回一个该数据类型的判断
        return Object.prototype.toString.call(value).slice(8,-1).toLowerCase()
    } //slice(8,-1)是将[object Number]中的[object ]去掉，只返回数据类型，toLowerCase()是不区分大小写
    //为什么要用toString方法呢，因为这个方法非常好用，对象调用toString方法能够直接访问到Object.prototype.toString里面，没有中间商赚差价


    let liList = document.querySelectorAll("li") //liList是类数组，没有数组方法，但是我们想要用数组方法怎么办？

    NodeList.prototype.reduce = Array.prototype.reduce //将数组原型上的reduce方法转接到类数组的原型上，使类数组在原型上也拥有数组方法

    console.log(liList.reduce((t, i) =>
        t + i.innerHTML
    ,"")) //初始值为空字符串，因为我们 liList类数组中的内容是li标签，将标签的innerHTML转化为字符串相加


</script>
</body>
</html>